Struct Vs Class When To Use Which
- π§ Conceptual Summary
- βοΈ Practical Explanation (How CLR Handles Them)
- π§© `struct`
- π§© `class`
- β‘ Performance and Design Implications
- β When to use `struct`
- π« When NOT to use `struct`
- β οΈ Boxing and Hidden Allocations
- π§© Real-World Example (HFM context)
- π§© Memory Visualization
- π― Senior-level 20-second summary (how to answer in interview)
- π§© Memory Behavior: `struct` vs `class`
- 1οΈβ£ Basic memory layout
- Questions & Answers
- 2οΈβ£ Assignment behavior
- β Struct (value type)
- β Class (reference type)
- 3οΈβ£ Struct inside a class (inline layout)
- 4οΈβ£ Passing to methods
- 5οΈβ£ Heap fragmentation and GC difference
- 6οΈβ£ βοΈ Summary Diagram
- π§ Quick βwhiteboard pitchβ for your interview
π§ Conceptual Summary
| Feature | struct | class |
|---|---|---|
| Type category | Value type | Reference type |
| Memory allocation | Stored inline (stack, or inside another object) | Stored on heap, referenced via pointer |
| Default behavior | Copied by value (creates a full copy) | Copied by reference (points to same object) |
| Nullability | Cannot be null (unless Nullable<T>) | Can be null |
| Inheritance | Cannot inherit from another struct or class; only from ValueType | Supports inheritance and polymorphism |
| Interfaces | Can implement interfaces | Can implement interfaces and base classes |
| Default constructor | Cannot define a custom parameterless constructor (C# 10 adds limited support) | Can freely define constructors |
| Finalizer / Destructor | Not supported | Supported |
| GC behavior | Usually short-lived, reclaimed when out of scope | Managed by the Garbage Collector |
| Boxing / Unboxing | Converting to/from object/interface causes allocation | No boxing/unboxing issues |
| Thread safety | Safer for small immutable data | Reference types require synchronization if shared |
---
βοΈ Practical Explanation (How CLR Handles Them)
π§© struct
- Lives inline β if itβs a local variable, itβs on the stack; if itβs a field in another object, itβs inside that objectβs memory layout.
- When passed to a method, a full copy is made (unless passed by
reforin). - Ideal for small, immutable, lightweight data β e.g., coordinates, ticks, prices, GUIDs.
Example:
struct Point
{
public int X;
public int Y;
}
Each Point lives inline β no GC pressure.
---
π§© class
- Lives on the managed heap. Variables hold a reference (pointer) to the actual object.
- Passed around by reference, so multiple variables can point to the same instance.
- Managed by the Garbage Collector.
Example:
class Order
{
public string Symbol { get; set; }
public double Price { get; set; }
}
Each Order allocation hits the heap and is tracked by the GC.
---
β‘ Performance and Design Implications
β
When to use struct
Use when:
- The object is small (β€ 16 bytes typically).
- Itβs immutable.
- Youβll create many of them (e.g., millions per second) and want no GC overhead.
- Value semantics make sense (copying creates independence).
Example (trading context):
readonly struct Tick
{
public string Symbol { get; }
public double Bid { get; }
public double Ask { get; }
}
Each Tick represents an immutable market data point. Perfect as a struct.
---
π« When NOT to use struct
Avoid when:
- Itβs large (lots of fields) β copying becomes expensive.
- You need polymorphism, inheritance, or shared references.
- You mutate the same instance in multiple places.
---
β οΈ Boxing and Hidden Allocations
When a struct is treated as an object or cast to an interface, it gets boxed β copied onto the heap.
struct Point { public int X, Y; }
object obj = new Point(); // BOXED: allocates on heap
Point p = (Point)obj; // UNBOXED: copy back to stack
So: value types are not automatically zero-GC β you must use them carefully.
---
π§© Real-World Example (HFM context)
If youβre processing millions of tick messages per second:
- Use a
struct(orreadonly struct) for individual ticks (lightweight, immutable). - Use a
classfor services and entities that manage state, likeOrderBook,TradeSession, orCacheManager.
Example:
readonly struct Tick
{
public string Symbol { get; init; }
public double Bid { get; init; }
public double Ask { get; init; }
}
class PriceFeedProcessor
{
private readonly List<Tick> _ticks = new();
public void OnTick(Tick tick) => _ticks.Add(tick);
}
---
π§© Memory Visualization
Stack:
ββ Tick t1 { X=1, Y=2 } (struct: inline)
ββ Tick t2 = t1 (copied!)
ββ Order ref ββ
βΌ
Heap:
ββ { Symbol="EURUSD", Price=1.0734 } (class: heap object)
---
π― Senior-level 20-second summary (how to answer in interview)
βA struct is a value type, stored inline and copied by value β great for small, immutable data and reducing GC pressure. A class is a reference type, stored on the heap, supporting inheritance and polymorphism. Structs avoid GC but can cost more to copy if large, and boxing them defeats their advantage. In low-latency systems, we often use small structs like
TickorPointto keep memory tight and predictable.β
---
Would you like me to give you 5 advanced interview questions around struct vs class (with answers), the kind might use to test your understanding of memory, GC, and performance?
Excellent β this visual explanation will make your answer stand out instantly in a senior-level C# interview (especially at, where they care about performance, GC, and data flow).
Letβs walk through it step by step, from memory layout to how structs and classes behave under assignment and method calls.
---
π§© Memory Behavior: struct vs class
1οΈβ£ Basic memory layout
ββββββββββββββββββββββββββββββββ
β Stack β
β βββββββββββββββββββββββββββ β
β β int x = 10; β β
β β Point p = {X=1,Y=2}; β β β Struct (value type)
β βββββββββββββββββββββββββββ β
β (lives inline here) β
β β
β βββββββββββββββββββββββββββ β
β β Order o βββββββββββββββββΌβββΌβββΊ Heap
β βββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββ
β Heap β
β βββββββββββββββββββββββββββ β
β β Order { Id=1, Price=99 }β β β Class (reference type)
β βββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββ
Explanation:
- Struct (
Point) is stored directly on the stack or inline within another object. - Class (
Order) is stored on the heap; variables on the stack hold a reference (pointer) to it.
---
Questions & Answers
Q: When should you choose a struct over a class?
A: When the data is small (β€16 bytes), immutable, frequently created, and benefits from value semantics. Structs reduce GC pressure by living inline and being collected with stack frames. Its data is stored on the stack (for locals), or inline inside another object (as part of that objectβs memory). This means
- No separate heap allocation
- No object header
- No pointer indirection
Stack memory is automatically reclaimed when a method returns. No garbage collection is needed for stack-allocated data. This is much cheaper than heap allocation + GC.
public readonly struct Money
{
public Money(decimal amount, string currency)
{
Amount = amount;
Currency = currency;
}
public decimal Amount { get; }
public string Currency { get; }
}
void Calculate()
{
Money price = new Money(100, "USD");
// price is on the stack
} // stack frame is popped β memory reclaimed immediately
β οΈ Structs are not always stack-allocated:
- If they are fields of a heap object, they live inside that object
- If they are boxed (cast to object or interface), they go to the heap
- Large structs copied often can hurt performance
Q: What pitfalls occur when structs are too large?
A: Copies become expensive, especially when passing by value. This can negate performance gains and increase stack usage. Use in/ref parameters or switch to classes if the struct grows.
Q: How does boxing affect struct performance?
A: Boxing copies the struct onto the heap and allocates, defeating the GC benefits. Avoid passing structs to APIs expecting object or non-generic interfaces to prevent boxing.
Q: Can structs have parameterless constructors?
A: Starting with C# 10, yes, but they must be public/private and initialize all fields. Historically, structs always had an implicit default constructor. Remember that every struct has a zeroed default state.
Q: How do you prevent copying when passing structs to methods?
A: Use in (readonly ref) for read-only access, or ref/ref readonly when you need to mutate or avoid copies. This keeps performance predictable for larger structs.
Q: Can structs inherit from classes?
A: No. Structs are sealed value types that inherit from ValueType. They can implement interfaces but cannot participate in class inheritance hierarchies.
Q: When do structs hurt cache locality?
A: Rarelyβthey often improve locality. However, large structs embedded in arrays can cause cache misses due to size. Evaluate data layout to ensure structs remain lean.
Q: How do you model optional structs?
A: Use Nullable<T> (Tick?). It wraps the struct with a HasValue flag, allowing null-like semantics without resorting to classes.
Q: What about mutability?
A: Prefer immutable structs to avoid accidental copies followed by mutation. Mutable structs can lead to confusing bugs when copies diverge silently.
Q: How do structs interact with pattern matching and deconstruction?
A: They support Deconstruct methods and pattern matching just like classes. This makes them ergonomic for lightweight domain data while still keeping value semantics.
---
2οΈβ£ Assignment behavior
β Struct (value type)
Point a = new Point { X = 1, Y = 2 };
Point b = a; // copy!
b.X = 99;
Console.WriteLine(a.X); // 1 (a unaffected)
Memory:
Stack:
a { X=1, Y=2 }
b { X=99, Y=2 } β completely separate copy
- Structs are copied by value.
- Each variable has its own independent copy.
- No heap allocation β no GC pressure.
---
β Class (reference type)
Order o1 = new Order { Id = 1, Price = 99 };
Order o2 = o1; // copy reference!
o2.Price = 120;
Console.WriteLine(o1.Price); // 120
Memory:
Stack:
o1 ββ
o2 βββββΊ Heap: { Id=1, Price=120 }
- Classes are copied by reference β both variables point to the same heap object.
- Modifying one affects the other.
---
3οΈβ£ Struct inside a class (inline layout)
class Trade
{
public string Symbol;
public Point Position;
}
Memory:
Heap: Trade
ββ Symbol β "EURUSD" (heap reference)
ββ Position { X=10, Y=20 } (inline in Trade object)
Insight: Even though the struct is inside a class (on heap), its fields are embedded inline β not separate allocations. This reduces pointer indirection and helps cache locality.
---
4οΈβ£ Passing to methods
void Move(Point p) { p.X += 10; } // copy!
void MoveRef(ref Point p) { p.X += 10; } // modifies original
Memory visualization:
By value (copy):
Caller: a { X=1 }
Method: p { X=1 } β modified to X=11 (copy destroyed)
By ref:
Caller: a { X=1 }
Method: p ββ
ββ modifies same memory β X=11 persists
π‘ Interview tip:
βStructs are copied on method calls unless passed by
reforin. Large structs should be passed byinto avoid copy overhead β especially in tight loops or latency-critical code.β
---
5οΈβ£ Heap fragmentation and GC difference
Structs:
[Stack]
[Stack frame destroyed β data gone instantly]
β No GC involvement.
Classes:
[Heap]
[Objects live until unreachable]
β GC scans and collects them (Gen0βGen1βGen2)
Key insight:
- Structs vanish when they go out of scope β predictable lifetime.
- Classes depend on GC cycles β non-deterministic reclamation.
- Overusing classes in a high-frequency path (like market ticks) causes GC churn and pauses.
---
6οΈβ£ βοΈ Summary Diagram
STRUCT (Value Type)
ββββββββββββββββ
β Inline Data β
ββββββββββββββββ€
β Copied on = β
ββββββββββββββββ€
β No GC β
ββββββββββββββββ€
β Pass by ref β
ββββββββββββββββ
β
Great for small immutable data
CLASS (Reference Type)
ββββββββββββββββ
β Heap Object β
ββββββββββββββββ€
β Copied ref β
ββββββββββββββββ€
β Managed by GCβ
ββββββββββββββββ€
β Supports OOP β
ββββββββββββββββ
β
Great for shared mutable state
---
π§ Quick βwhiteboard pitchβ for your interview
βStructs are value types β stored inline, copied by value, no GC involvement, ideal for small immutable data like ticks or coordinates. Classes are reference types β heap-allocated, reference-based, and managed by GC. I use structs where I want predictable lifetimes and zero allocations; classes when I need shared, long-lived state or polymorphism.β
---
Would you like me to now create a visual of memory layout with stack/heap arrows (an actual diagram you could memorize or even sketch during the interview)? It would show struct, class, and mixed cases (struct-in-class, class-in-struct) clearly.